package com.gitee.wsl.compose.chart.xy


import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.layout.SubcomposeLayout
import com.gitee.wsl.common.ui.draw.style.LineStyle
import com.gitee.wsl.common.ui.ext.normalizeCentroid
import com.gitee.wsl.common.ui.ext.normalizePan
import com.gitee.wsl.compose.chart.ExperimentalKoalaPlotApi
import com.gitee.wsl.compose.chart.KoalaPlotTheme
import com.gitee.wsl.compose.chart.xy.axis.style.Axis
import com.gitee.wsl.compose.chart.xy.axis.style.AxisDelegate
import com.gitee.wsl.compose.chart.xy.axis.AxisModel
import com.gitee.wsl.compose.chart.xy.axis.style.AxisStyle
import com.gitee.wsl.compose.chart.xy.axis.style.rememberAxisStyle
import com.gitee.wsl.compose.chart.xy.layout.Measurables
import com.gitee.wsl.compose.chart.xy.layout.XYAxisMeasurePolicy
import com.gitee.wsl.compose.chart.xy.axis.AxisLabel
import com.gitee.wsl.compose.chart.xy.axis.AxisTitle
import com.gitee.wsl.compose.modifier.input.gestures.GestureConfig
import com.gitee.wsl.compose.ui.container.hoverable.HoverableElementArea
import com.gitee.wsl.compose.modifier.layout.VerticalRotation
import com.gitee.wsl.compose.modifier.layout.rotateVertically
import com.gitee.wsl.compose.modifier.input.gestures.onGestureInput

/**
 * Provides a set of X-Y axes and grid for displaying X-Y plots.
 *
 * @param X The data type for the x-axis
 * @param Y The data type for the y-axis
 * @param xAxisModel x-axis state controlling the display of the axis and coordinate transformation
 * @param yAxisModel y-axis state controlling the display of the axis and coordinate transformation
 * @param xAxisStyle Style for the x-axis
 * @param xAxisLabels Composable to display labels for specific x-axis values
 * @param xAxisTitle Title for the X-axis
 * @param yAxisStyle Style for the y-axis
 * @param yAxisLabels Composable to display labels for specific y-axis values
 * @param yAxisTitle Title for the y-axis
 * @param panZoomEnabled True if the plot can be panned and zoomed, false to disable. Enabling panning and zooming may
 * interfere with scrolling a parent container if the drag point is on the plot.
 * @param content The content to be displayed, which should include one plot for each series to be
 * plotted on this XYGraph.
 */
@Composable
 fun <X, Y> XYGraph(
    xAxisModel: AxisModel<X>,
    yAxisModel: AxisModel<Y>,
    modifier: Modifier = Modifier,
    xAxisStyle: AxisStyle = rememberAxisStyle(),
    xAxisLabels: @Composable (X) -> Unit,
    xAxisTitle: @Composable () -> Unit = {},
    yAxisStyle: AxisStyle = rememberAxisStyle(),
    yAxisLabels: @Composable (Y) -> Unit,
    yAxisTitle: @Composable () -> Unit = {},
    horizontalMajorGridLineStyle: LineStyle? = KoalaPlotTheme.axisThemeStyle.majorGridlineStyle,
    horizontalMinorGridLineStyle: LineStyle? = KoalaPlotTheme.axisThemeStyle.minorGridlineStyle,
    verticalMajorGridLineStyle: LineStyle? = KoalaPlotTheme.axisThemeStyle.majorGridlineStyle,
    verticalMinorGridLineStyle: LineStyle? = KoalaPlotTheme.axisThemeStyle.minorGridlineStyle,
    gestureConfig: GestureConfig = GestureConfig(),
    content: @Composable XYGraphScope<X, Y>.() -> Unit
) {
    HoverableElementArea(modifier = modifier) {
        SubcomposeLayout { constraints ->
            val xAxisTitleMeasurable = subcompose("xaxistitle") {
                Box(modifier = Modifier.fillMaxWidth()) { xAxisTitle() }
            }[0]

            val yAxisTitleMeasurable = subcompose("yaxistitle") {
                Box(modifier = Modifier.fillMaxHeight()) { yAxisTitle() }
            }[0]

            // Computing the tick values isn't exact because composables can't be measured twice
            // and there's no way to compute the height/width of the axis tick labels before knowing
            // how many tick labels there should be. So we leave them out of the calculation for
            // the axis length constraints, meaning the actual min distance of the axis tick labels
            // may be closer than specified because the actual axis lengths computed later, which
            // take into account the axis label sizes, will be smaller than the lengths computed here.
            val xAxis = AxisDelegate.createHorizontalAxis(
                xAxisModel,
                xAxisStyle,
                (constraints.maxWidth - -yAxisTitleMeasurable.maxIntrinsicWidth(constraints.maxHeight)).toDp()
            )

            val yAxis = AxisDelegate.createVerticalAxis(
                yAxisModel,
                yAxisStyle,
                (constraints.maxHeight - -xAxisTitleMeasurable.maxIntrinsicHeight(constraints.maxWidth)).toDp()
            )

            val xAxisMeasurable = subcompose("xaxis") { Axis(xAxis) }[0]
            val yAxisMeasurable = subcompose("yaxis") { Axis(yAxis) }[0]

            val chartMeasurable = subcompose("chart") {
                val panZoomModifier = if (gestureConfig.gesturesEnabled) {
                    Modifier.onGestureInput(
                        key1 = xAxisModel,
                        key2 = yAxisModel,
                        gestureConfig = gestureConfig,
                        onZoomChange = { size, centroid, zoom ->
                            val normalizedCentroid = centroid.normalizeCentroid(size)
                            zoomAxis(xAxisModel, size.width, normalizedCentroid.x, zoom.x)
                            zoomAxis(yAxisModel, size.height, normalizedCentroid.y, zoom.y)
                        },
                        onPanChange = { size, pan ->
                            val normalizedPan = pan.normalizePan()
                            val xPanChanged = panAxis(xAxisModel, size.width, normalizedPan.x)
                            val yPanChanged = panAxis(yAxisModel, size.height, normalizedPan.y)

                            val allowXPanConsumption = xPanChanged && gestureConfig.panXConsumptionEnabled
                            val allowYPanConsumption = yPanChanged && gestureConfig.panYConsumptionEnabled

                            return@onGestureInput allowXPanConsumption || allowYPanConsumption
                        }
                    )
                } else {
                    Modifier
                }

                Box(
                    modifier = Modifier.clip(RectangleShape).then(panZoomModifier)
                ) {
                    val chartScope = XYGraphScopeImpl(xAxisModel, yAxisModel, xAxis, yAxis, this@HoverableElementArea)
                    chartScope.content()
                }
            }[0]

            val measurables = subcompose(Unit) {
                Grid(
                    xAxis,
                    yAxis,
                    horizontalMajorGridLineStyle,
                    horizontalMinorGridLineStyle,
                    verticalMajorGridLineStyle,
                    verticalMinorGridLineStyle
                )

                xAxis.majorTickValues.forEach {
                    Box(modifier = Modifier.rotate(-xAxisStyle.labelRotation.toFloat())) { xAxisLabels(it) }
                }
                yAxis.majorTickValues.forEach {
                    Box(modifier = Modifier.rotate(-yAxisStyle.labelRotation.toFloat())) { yAxisLabels(it) }
                }
            }

            val measurablesMap = Measurables(
                measurables[0],
                chartMeasurable,
                xAxisMeasurable,
                measurables.subList(1, xAxis.majorTickValues.size + 1),
                xAxisTitleMeasurable,
                yAxisMeasurable,
                measurables.subList(
                    xAxis.majorTickValues.size + 1,
                    xAxis.majorTickValues.size + 1 + yAxis.majorTickValues.size
                ),
                yAxisTitleMeasurable
            )

            with(XYAxisMeasurePolicy(xAxis, yAxis)) {
                measure(measurablesMap, constraints)
            }
        }
    }
}


private fun <T> zoomAxis(
    axis: AxisModel<T>,
    length: Int,
    centroid: Float,
    zoom: Float
) {
    val pivot = centroid / length.toFloat()
    axis.zoom(zoom, pivot.coerceIn(0f, 1f))
}

private fun <T> panAxis(
    axis: AxisModel<T>,
    length: Int,
    pan: Float
): Boolean {
    return axis.pan(pan / length.toFloat())
}

/**
 * An XYGraph overload that takes Strings for axis labels and titles instead of Composables for use cases where
 * custom styling is not required.
 *
 * Provides a set of X-Y axes and grid for displaying an X-Y plot.
 *
 * @param X The data type for the x-axis
 * @param Y The data type for the y-axis
 * @param xAxisModel x-axis state controlling the display of the axis and coordinate transformation
 * @param yAxisModel y-axis state controlling the display of the axis and coordinate transformation
 * @param xAxisStyle Style for the x-axis
 * @param xAxisLabels String factory of x-axis label Strings
 * @param xAxisTitle Title for the X-axis
 * @param yAxisStyle Style for the y-axis
 * @param yAxisLabels String factory of y-axis label Strings
 * @param yAxisTitle Title for the y-axis
 * @param panZoomEnabled True if the plot can be panned and zoomed, false to disable. Enabling panning and zooming may
 * interfere with scrolling a parent container if the drag point is on the plot.
 * @param content The content to be displayed within this graph, which should include one plot for each
 * data series to be plotted.
 */
@ExperimentalKoalaPlotApi
@Composable
fun <X, Y> XYGraph(
    xAxisModel: AxisModel<X>,
    yAxisModel: AxisModel<Y>,
    modifier: Modifier = Modifier,
    xAxisStyle: AxisStyle = rememberAxisStyle(),
    xAxisLabels: (X) -> String = xAxisModel::label,
    xAxisTitle: String? = null,
    yAxisStyle: AxisStyle = rememberAxisStyle(),
    yAxisLabels: (Y) -> String = yAxisModel::label,
    yAxisTitle: String? = null,
    horizontalMajorGridLineStyle: LineStyle? = KoalaPlotTheme.axisThemeStyle.majorGridlineStyle,
    horizontalMinorGridLineStyle: LineStyle? = KoalaPlotTheme.axisThemeStyle.minorGridlineStyle,
    verticalMajorGridLineStyle: LineStyle? = KoalaPlotTheme.axisThemeStyle.majorGridlineStyle,
    verticalMinorGridLineStyle: LineStyle? = KoalaPlotTheme.axisThemeStyle.minorGridlineStyle,
    gestureConfig: GestureConfig = GestureConfig(),
    content: @Composable XYGraphScope<X, Y>.() -> Unit
) {
    XYGraph(
        xAxisModel,
        yAxisModel,
        modifier,
        xAxisStyle,
        xAxisLabels = {
            /*Text(
                xAxisLabels(it),
                color = MaterialTheme.colorScheme.onBackground,
                style = MaterialTheme.typography.bodySmall,
                modifier = Modifier.padding(top = 2.dp)
            )*/
            AxisLabel(label = xAxisLabels(it))
        },
        xAxisTitle = {
            if (xAxisTitle != null) {
                AxisTitle(title = xAxisTitle)
                /*Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
                    Text(
                        xAxisTitle,
                        color = MaterialTheme.colorScheme.onBackground,
                        style = MaterialTheme.typography.titleMedium
                    )
                }*/
            }
        },
        yAxisStyle,
        yAxisLabels = {
            /*Text(
                yAxisLabels(it),
                color = MaterialTheme.colorScheme.onBackground,
                style = MaterialTheme.typography.bodySmall,
                modifier = Modifier.padding(top = 2.dp)
            )*/
            AxisLabel(label = yAxisLabels(it))
        },
        yAxisTitle = {
            if (yAxisTitle != null) {
                AxisTitle(title = yAxisTitle, modifier = Modifier
                    .rotateVertically(VerticalRotation.COUNTER_CLOCKWISE)
                    .padding(bottom = KoalaPlotTheme.plotSizes.gap))
                /*Box(modifier = Modifier.fillMaxHeight(), contentAlignment = Alignment.Center) {
                    Text(
                        yAxisTitle,
                        color = MaterialTheme.colorScheme.onBackground,
                        style = MaterialTheme.typography.titleMedium,
                        modifier = Modifier
                            .rotateVertically(VerticalRotation.COUNTER_CLOCKWISE)
                            .padding(bottom = KoalaPlotTheme.plotSizes.gap),
                    )
                }*/
            }
        },
        horizontalMajorGridLineStyle,
        horizontalMinorGridLineStyle,
        verticalMajorGridLineStyle,
        verticalMinorGridLineStyle,
        gestureConfig,
        content
    )
}
